/*
 * Copyright 2011 Sven Buschbeck.
 * 
 * This file is part of the ActiveObjects library.
 *
 *  ActiveObjects is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  ActiveObjects is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.svenbuschbeck.gwt.ao.client;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import net.svenbuschbeck.gwt.ao.client.events.HasValueAddedHandlers;
import net.svenbuschbeck.gwt.ao.client.events.HasValueChangedHandlers;
import net.svenbuschbeck.gwt.ao.client.events.HasValueRemovedHandlers;
import net.svenbuschbeck.gwt.ao.client.events.ValueAddedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueAddedHandler;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedHandler;
import net.svenbuschbeck.gwt.ao.client.events.ValueRemovedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueRemovedHandler;

import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;

/**
 * Maintains a list of widgets, fires valueChangeEvents if an view is added
 * 
 * @author Sven Buschbeck
 * 
 * @param <T>
 * 
 *            OPTIMIZE Change HandlerManager (deprecated) to whatever it is
 *            supposed to be now.
 */
@SuppressWarnings("deprecation") public class ActiveList<T> implements HasValueAddedHandlers<T>,
	HasValueRemovedHandlers<T>, HasValueChangedHandlers<List<T>>, Iterable<T>, List<T> {

	public abstract class ReadOnlyAddValueHook implements ReadOnlyHook<T> {

		@Override public void commitValue(T value) {
			_add(value);
		}
	}

	public abstract class ReadOnlyRemoveValueHook implements ReadOnlyHook<T> {

		@Override public void commitValue(T value) {
			_remove(value);
		}
	}

	private LinkedList<T> collection = new LinkedList<T>();
	private T[] dummyForConvertion = null;
	private HandlerManager manager = new HandlerManager(null);
	private final boolean valuesHaveToBeUnique;
	private final boolean valuesCanBeNull;
	private Long maxSize = null;
	private final ReadOnlyAddValueHook readOnlyAddValueHook;
	private final ReadOnlyRemoveValueHook readOnlyRemoveValueHook;

	/**
	 * Creates ActiveCollection setting uniqueValues=false.
	 */
	public ActiveList() {
		this(false);
	}

	/**
	 * Creates an ActiveCollection requiring all it's elements to be a.equals(b)
	 * == false.
	 * 
	 * @param uniqueValues
	 */
	public ActiveList(boolean uniqueValues) {
		this(uniqueValues, true);
	}

	/**
	 * Creates ActiveCollection setting uniqueValues=false.
	 * 
	 * @param adoptCollection
	 */
	public ActiveList(Collection<T> adoptCollection, ReadOnlyAddValueHook readOnlyAddValueHook,
		ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
		this(false, adoptCollection, readOnlyAddValueHook, readOnlyRemoveValueHook);
	}

	/**
	 * Creates ActiveCollection setting valuesCanBeNull=true.
	 * 
	 * @param valuesHaveToBeUnique
	 * @param adoptCollection
	 */
	public ActiveList(boolean valuesHaveToBeUnique, Collection<T> adoptCollection) {
		this(valuesHaveToBeUnique, adoptCollection, null, null);
	}

	/**
	 * Creates ActiveCollection setting valuesCanBeNull=true.
	 * 
	 * @param valuesHaveToBeUnique
	 * @param adoptCollection
	 */
	public ActiveList(boolean valuesHaveToBeUnique, Collection<T> adoptCollection,
		ReadOnlyAddValueHook readOnlyAddValueHook, ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
		this(valuesHaveToBeUnique, true, adoptCollection, readOnlyAddValueHook, readOnlyRemoveValueHook);
	}

	/**
	 * Creates ActiveCollection setting initial collection to null (will cause
	 * creating an empty collection).
	 * 
	 * @param valuesHaveToBeUnique
	 * @param valuesCanBeNull
	 */
	public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull) {
		this(valuesHaveToBeUnique, valuesCanBeNull, null, null);
	}

	/**
	 * Creates ActiveCollection setting initial collection to null (will cause
	 * creating an empty collection).
	 * 
	 * @param valuesHaveToBeUnique
	 * @param valuesCanBeNull
	 */
	public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, ReadOnlyAddValueHook readOnlyAddValueHook,
		ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
		this(valuesHaveToBeUnique, valuesCanBeNull, null, readOnlyAddValueHook, readOnlyRemoveValueHook);
	}

	/**
	 * Creates ActiveCollection setting trickerValueAddedEventForAll=false.
	 * 
	 * @param valuesHaveToBeUnique
	 * @param valuesCanBeNull
	 * @param adoptCollection
	 */
	public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, Collection<T> adoptCollection) {
		this(valuesHaveToBeUnique, valuesCanBeNull, adoptCollection, null, null);
	}

	/**
	 * Creates ActiveCollection setting trickerValueAddedEventForAll=false.
	 * 
	 * @param valuesHaveToBeUnique
	 * @param valuesCanBeNull
	 * @param adoptCollection
	 */
	public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, Collection<T> adoptCollection,
		ReadOnlyAddValueHook readOnlyAddValueHook, ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
		this(valuesHaveToBeUnique, valuesCanBeNull, adoptCollection, false, readOnlyAddValueHook,
			readOnlyRemoveValueHook);
	}

	/**
	 * Creates ActiveCollection with all parameters there are
	 * 
	 * @param valuesHaveToBeUnique
	 * @param valuesCanBeNull
	 * @param adoptCollection
	 * @param trickerValueAddedEventForAll
	 */
	public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, Collection<T> adoptCollection,
		boolean trickerValueAddedEventForAll, ReadOnlyAddValueHook readOnlyAddValueHook,
		ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
		this.valuesHaveToBeUnique = valuesHaveToBeUnique;
		this.valuesCanBeNull = valuesCanBeNull;
		this.readOnlyAddValueHook = readOnlyAddValueHook;
		this.readOnlyRemoveValueHook = readOnlyRemoveValueHook;
		adopt(adoptCollection, trickerValueAddedEventForAll);
	}

	public void adopt(Collection<T> adoptCollection, boolean trickerValueAddedEventForAll) {
		collection = (adoptCollection != null) ? new LinkedList<T>(adoptCollection) : new LinkedList<T>();
		if (trickerValueAddedEventForAll) {
			trickerValueAddedEventForAll();
		}
	}

	private void trickerValueAddedEventForAll() {
		// TODO Add option to use Scheduler.get().scheduleIncremental() to fire each event using a scheduler not to block UI
		for (T value : collection) {
			ValueAddedEvent.fire(this, value);
		}
		fireValueChangeEvent(new LinkedList<T>(), getStaticCopy());
	}

	@Override public void fireEvent(GwtEvent<?> event) {
		manager.fireEvent(event);
	}

	/**
	 * Adds and returns e. Returning e allows to concatenate further operations
	 * on e, e.g. added e to two collections in one line:
	 * collection2.add(collection.add(e));
	 * 
	 * @param e
	 * @return
	 */
	public T addAndReturn(T e) {
		add(e);
		return e;
	}

	@Override public boolean add(T e) {
		if (readOnlyAddValueHook != null) {
			readOnlyAddValueHook.onNewValue(e);
			return true;
		}
		return _add(e);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#add(int, java.lang.Object)
	 */
	@Override public void add(int arg0, T arg1) {

	}

	private boolean _add(T e) {
		List<T> before = getStaticCopy();
		boolean gotAdded = ValueAddedEvent.addAndFireIfRequired(this, collection, e, valuesHaveToBeUnique,
			valuesCanBeNull, maxSize);
		if (gotAdded) {
			fireValueChangeEvent(before, getStaticCopy());
		}
		return gotAdded;
	}

	/**
	 * Removes given object from collection. If contained, ValueRemovedEvent
	 * gets fired.
	 * 
	 * @param e
	 * @return e
	 */
	public T removeAndReturn(T e) {
		_remove(e);
		return e;
	}

	@SuppressWarnings("unchecked") @Override public boolean remove(Object e) {
		if (readOnlyRemoveValueHook != null) {
			readOnlyAddValueHook.onNewValue((T) e);
			return true;
		}
		return _remove((T) e);
	}

	private boolean _remove(T e) {
		List<T> before = getStaticCopy();
		if (ValueRemovedEvent.removeAndFireIfNotNull(this, collection, e)) {
			fireValueChangeEvent(before, getStaticCopy());
			return true;
		}
		return false;
	}

	private void fireValueChangeEvent(List<T> before, List<T> after) {
		ValueChangedEvent.fire(this, before, after);
	}

	@Override public HandlerRegistration addValueAddedHandler(ValueAddedHandler<T> handler) {
		return addValueAddedHandler(handler, false);
	}

	public HandlerRegistration addValueAddedHandler(ValueAddedHandler<T> handler, boolean trickerValueAddedEventForAll) {
		HandlerRegistration handlerRegistration = manager.addHandler(ValueAddedEvent.getType(), handler);
		if (trickerValueAddedEventForAll) {
			trickerValueAddedEventForAll();
		}
		return handlerRegistration;
	}

	@Override public HandlerRegistration addValueRemovedHandler(ValueRemovedHandler<T> handler) {
		return manager.addHandler(ValueRemovedEvent.getType(), handler);
	}

	public HandlerRegistration addValueChangedHandler(ValueChangedHandler<List<T>> valueChangedHandler) {
		return addValueChangedHandler(valueChangedHandler, false);
	};

	/*
	 * (non-Javadoc)
	 * 
	 * @seenet.svenbuschbeck.gwt.ao.client.events.HasValueChangedHandlers#
	 * addValueChangedHandler
	 * (net.svenbuschbeck.gwt.ao.client.events.ValueChangedHandler, boolean)
	 */
	@Override public HandlerRegistration addValueChangedHandler(ValueChangedHandler<List<T>> valueChangedHandler,
		boolean triggerInitially) {
		HandlerRegistration handlerRegistration = manager.addHandler(ValueChangedEvent.getType(), valueChangedHandler);
		List<T> staticCopy = getStaticCopy();
		ValueChangedEvent.fire(this, staticCopy, staticCopy);
		return handlerRegistration;
	}

	// Implement Iterator<T> //////////////////////////////////////////////////

	@Override public Iterator<T> iterator() {
		// Can cause CooncurrentUpdateExceptions, so it's always better to iterate over a copy
		//		return collection.iterator();
		return getStaticCopy().iterator();
	}

	// Implement Collection<T> ////////////////////////////////////////////////

	@Override public int size() {
		return collection.size();
	}

	@Override public void clear() {
		// iterating on the copy to avoid java.util.ConcurrentModificationException as the removeValue events might cause the collection to be changed in parallel
		//		T[] copy = (T[]) collection.toArray();
		//		for (T element : copy) {
		//			remove(element);
		//		}

		// idea of copying collection implemented in forEach method 
		forEach(new ForEach<T>() {

			@Override public void execute(T element) {
				remove(element);
			}
		});
	}

	@Override public boolean addAll(Collection<? extends T> c) {
		int size = collection.size();
		for (T element : c) {
			add(element);
		}
		return size != collection.size();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#addAll(int, java.util.Collection)
	 */
	@Override public boolean addAll(int arg0, Collection<? extends T> arg1) {
		List<T> before = getStaticCopy();
		collection.addAll(arg0, arg1);
		boolean changed = before.size() != collection.size();
		for (T obj : arg1) {
			ValueAddedEvent.fire(this, obj);
		}
		if (changed) {
			fireValueChangeEvent(collection, collection);
		}
		return changed;
	}

	@Override public boolean contains(Object o) {
		return collection.contains(o);
	}

	@Override public boolean containsAll(Collection<?> c) {
		return collection.containsAll(c);
	}

	@Override public boolean isEmpty() {
		return collection.isEmpty();
	}

	@Override public boolean removeAll(Collection<?> c) {
		int size = collection.size();
		for (Object element : c) {
			remove(element);
		}
		return size != collection.size();
	}

	@Override public boolean retainAll(final Collection<?> c) {
		int size = collection.size();
		//		for (T element : collection) {
		//			if (!c.contains(element)) {
		//				remove(element);
		//			}
		//		}
		forEach(new ForEach<T>() {

			@Override public void execute(T element) {
				if (!c.contains(element)) {
					remove(element);
				}
			}
		});
		return size != collection.size();
	}

	@Override public Object[] toArray() {
		return collection.toArray();
	}

	@Override public <T2> T2[] toArray(T2[] a) {
		return collection.toArray(a);
	}

	/**
	 * Use this method to iterate over all elements in this collection avoiding
	 * "concurrent change" exceptions.
	 * 
	 * @param forEach
	 *            callback method
	 */
	public void forEach(ForEach<T> forEach) {
		// copy collection to not run into "concurrent change" exceptions.
		//		for (T object : new ArrayList<T>(collection)) {
		Collection<T> staticCopy = getStaticCopy();
		if (staticCopy != null) {
			for (T object : staticCopy) {
				forEach.execute(object);
			}
		}
	}

	/**
	 * Returns a static copy of the containing collection of objects. Use this
	 * collection to iterate over it over time to avoid "concurrent change"
	 * exceptions.
	 * 
	 * @return Independent copy of internal collection.
	 */
	public List<T> getStaticCopy() {
		assert collection != null : "Inner collection must not be null.";
		return new ArrayList<T>(collection);
	}

	/**
	 * @param maximumNumberOfCanvasMedia
	 */
	public void setMaxSize(Long maximumNumberOfElements) {
		this.maxSize = Math.max(0, maximumNumberOfElements);
		while (size() > maximumNumberOfElements) {
			pop();
		}
	}

	/**
	 * Removes and returns last element in collection
	 */
	public T pop() {
		return removeAndReturn(collection.getLast());
	}

	public Long getMaxSize() {
		return maxSize;
	}

	public void clearMaxSize() {
		maxSize = null;
	}

	/**
	 * @return
	 */
	public boolean hasFreeSlots() {
		return size() < getMaxSize();
	}

	/**
	 * Returns the element at the given index. Makes use of direct access if
	 * containing collection is a list or converts collection to array
	 * otherwise.
	 * 
	 * @param index
	 * @return
	 * @throws IndexOutOfBoundsException
	 */
	public T get(int index) {
		if (collection instanceof List) {
			return ((List<T>) collection).get(index);
		}
		return collection.toArray(dummyForConvertion)[index];
	}

	/**
	 * Returns the index of the given object in this collection or -1 if it is
	 * not contained. Makes use of direct access if containing collection is a
	 * list or iterates otherwise
	 * 
	 * @param object
	 * @return index of the given object or -1 if the object is not contained in
	 *         this collection
	 */
	@Override public int indexOf(Object object) {
		//		if (object != null) {
		//			if (collection instanceof List) {
		return ((List<T>) collection).indexOf(object);
		//			}
		//			T[] array = collection.toArray(dummyForConvertion);
		//			for (int x = 0; x < array.length; x++) {
		//				if (object.equals(array[x])) {
		//					return x;
		//				}
		//			}
		//		}
		//		return -1;
	}

	/**
	 * Returns the first element that is object.equals(element)
	 * 
	 * @param object
	 * @return element that equals the given object.
	 */
	public T getFirstObjectEqualTo(T object) {
		int index = indexOf(object);
		if (index >= 0) {
			return get(index);
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override public String toString() {
		return "ActiveCollection(size=" + size() + "; maxSize=" + getMaxSize() + ")";
	}

	public boolean isReadOnly() {
		return readOnlyAddValueHook != null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#lastIndexOf(java.lang.Object)
	 */
	@Override public int lastIndexOf(Object arg0) {
		return collection.lastIndexOf(arg0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#listIterator()
	 */
	@Override public ListIterator<T> listIterator() {
		return collection.listIterator();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#listIterator(int)
	 */
	@Override public ListIterator<T> listIterator(int arg0) {
		return collection.listIterator(arg0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#remove(int)
	 */
	@Override public T remove(int index) {
		T removed = collection.remove(index);
		if (removed != null) {
			ValueRemovedEvent.fire(this, removed);
		}
		return removed;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#set(int, java.lang.Object)
	 */
	@Override public T set(int index, T newObject) {
		T oldObject = collection.get(index);
		if (compare(oldObject, newObject)) {
			return oldObject;
		}
		remove(oldObject);
		add(index, newObject);

		return newObject;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.List#subList(int, int)
	 */
	@Override public List<T> subList(int arg0, int arg1) {
		return collection.subList(arg0, arg1);
	}

	private boolean compare(T object1, T object2) {
		return object1 == object2 || (object1 != null && object1.equals(object2));
	}
}
